Stage, Sceneはパッケージだが、JavaDocを見てもSceneGraphというパッケージは存在しない。どうやら、Scene.Nodeクラスを継承したクラスがSceneGraphと呼ばれている模様。今回は、SceneGraphの全体像を見ていく。
■ SceneGraph( Scene.Nodeクラス )の基本
Scene Graphには多くのクラスが属しているが、すべてのクラスについて以下のルールが適用される。
データ構造の制約
各ノードは1つ以上の親を持てない。もし、親を新しく設定した場合、かつての親との関係はリセットされる。木構造内にループが発生するような操作は無視され、例外が発生する。
ID
ノードにはID(String型)が設定でき、lookup(String)でノードの呼び出しができる。IDは一意になるようにプログラマが設定する。
座標系
画面左上が(x,y)=(0,0)。右に行くほどx座標の値が、下に行くほどy座標の値が増える。3Dの場合、z軸は画面に垂直方向に伸び、奥に置くほどz座標の値が増える。座標は整数(Integer)でなく浮動小数点数(float)で指定する。
座標変換
平行移動・回転・拡大縮小・せん断(shearing)が可能。
その他
シェイプのバウンディングボックスは、外枠を含めた大きさとなる。外枠に太さが設定されている場合、デフォルトではシェイプの内外にそれぞれ外枠の太さの半分の厚みが追加される。
また、各ノードはstyleClassを持ち、CSSと同様の内容を保持している。
■ SceneGraphに属するクラス
SceneGraphはクラス数が多いため、すべて列挙することはできないが代表的なものを上げると以下のように分類できる。すべてScene.Nodeクラスを継承しているため、どのようなクラスがあるか知りたい場合にはJava DocのScene.Nodeクラスからたどっていけばよい。
分類
(type) |
基底クラス
(base class) |
主要な具象クラス
(main concrete class) |
内容
(content) |
一般 |
Parent |
Group
Button
Label
GridPane
PieChartなど |
子ノードを保持できるクラス。
レイアウト・コンテナやチャート(グラフ)、UIコントロールなどが該当する。 |
SubScene |
SubScene |
シーンの中に別のシーンを表示するためのクラス。
2Dと3Dは1つのシーン内で同時に描画できないため、
SubSceneを使ってシーンを分ける。 |
2D |
Canvas |
Canvas |
Swingと同じような描画関数が使えるクラス。
GraphicsContextを用いた描画を行う。 |
Shape |
Line
Circle
Polygon
Rectangle
Textなど |
2次元シェイプを出力するクラス。 |
ImageView |
ImageView |
画像を出力するクラス。 |
MediaView |
MediaView |
メディアを出力するクラス。 |
SwingNode |
SwingNode |
SwingコンテンツをJavaFXに埋め込むために使うクラス |
3D |
Shape3D |
Box
Cylinder
MeshView
Sphereなど |
3次元シェイプを出力するクラス。 |
Camera |
ParallelCamera
PerspectiveCamera |
3Dカメラを表すクラス。 |
LightBase |
AmbientLight
PointLight |
3D照明を表すクラス。 |
Scene Graphは名前を見ただけで大体どのようなものかわかると思われる。
利用の際に注意すべき点は、『2Dオブジェクトと3Dオブジェクトは、1つのシーン内で併用できない』という点である。利用しようとしているクラスが2D/3Dのどちらに分類されるかはプログラマが意識する必要がある。ただしSubSceneクラスを利用することで、1つのウィンドウ内に2Dと3Dの併用が可能となる。
Scene Graphを用いたサンプルプログラムを以下に示す。サンプルでは3Dオブジェクトである立方体と、2Dオブジェクトであるメッセージを同時に出力している。
◇サンプルプログラムの実行結果
◇サンプルプログラム
package application_fx;
import java.io.File;
import javafx.application.Application;
import javafx.geometry.Point3D;
import javafx.scene.Camera;
import javafx.scene.Group;
import javafx.scene.LightBase;
import javafx.scene.PerspectiveCamera;
import javafx.scene.PointLight;
import javafx.scene.Scene;
import javafx.scene.SubScene;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.paint.PhongMaterial;
import javafx.scene.shape.Box;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Rectangle;
import javafx.scene.text.Font;
import javafx.scene.text.Text;
import javafx.stage.Stage;
public class TestSceneGraph extends Application {
public static void main(String[] args)
{
launch( args );
}
/**
* 【シーングラフの構成】
*
* root
* ┣ sg3D
* ┃ ┣ box
* ┃ ┃ ┗material
* ┃ ┣ camera
* ┃ ┗ light
* ┃
* ┗ sg2D
* ┣ rect
* ┣ circle
* ┣ image
* ┗ text
*
*/
@Override
public void start(Stage primaryStage) throws Exception
{
// 3Dシーンを作成
SubScene sg3D = create3DScene();
// 2Dシーンを作成
SubScene sg2D = create2DScene();
// シーングラフを構成
Pane root = new Pane();
root.getChildren().add( sg3D );
root.getChildren().add( sg2D );
sg2D.relocate( 10 , sg3D.getHeight() - sg2D.getHeight() - 10 );
// シーンを作成
Scene scene = new Scene( root );
// ステージの作成
primaryStage.setScene( scene );
primaryStage.setTitle( "Scene Graphのテスト" );
primaryStage.show();
}
/**
* 3Dシーンを作成
* @return
*/
private SubScene create3DScene()
{
// 3Dシーングラフのルートを作成
Group sg3D = new Group();
// 立方体を追加
Box box = new Box( 10 , 10 , 10 );
box.setRotationAxis( new Point3D( 1.0 , 1.0 , 1.0 ) );
box.setRotate( 30.0 );
sg3D.getChildren().add( box );
// 立方体の色を設定
PhongMaterial material = new PhongMaterial();
material.setDiffuseColor( Color.AZURE );
box.setMaterial( material );
// カメラ設定
Camera camera = new PerspectiveCamera( true );
camera.setTranslateZ( -30.0 );
sg3D.getChildren().add( camera );
// 照明設定(スポットライト)
LightBase light = new PointLight();
light.setTranslateX( 20.0 );
light.setTranslateY( -20.0 );
light.setTranslateZ( -30.0 );
sg3D.getChildren().add( light );
// シーンを作成
SubScene subscene = new SubScene( sg3D , 400 , 400 );
subscene.setFill(Color.BLACK);
subscene.setCamera( camera );
return subscene;
}
/**
* 2Dシーンを作成
* @return
*/
private SubScene create2DScene()
{
// 2Dシーングラフのルートを作成
Pane sg2D = new Pane();
// 長方形を追加
Rectangle rect = new Rectangle( 200 , 50 );
rect.setArcHeight( 10 );
rect.setArcWidth( 10 );
rect.setFill( Color.GREENYELLOW );
rect.setOpacity( 0.75 );
sg2D.getChildren().add( rect );
// 円を追加
Circle circle = new Circle( 17.5 );
circle.setLayoutX( 25.0 );
circle.setLayoutY( 25.0 );
circle.setFill( Color.WHITE );
sg2D.getChildren().add( circle );
// 画像を追加
ImageView image = new ImageView();
Image img = new Image( new File("img/cursor.png").toURI().toString() );
image.setImage( img );
image.setSmooth( true );
image.setPreserveRatio( true );
image.setFitWidth( 30 );
image.setLayoutX( 10.0 );
image.setLayoutY( 10.0 );
sg2D.getChildren().add( image );
// テキストを追加
Text text = new Text();
text.setText( "JavaFX 8からはテキストの自動改行(折り返し)機能が追加されています。" );
text.setFont( new Font( 12 ) );
text.setFill( Color.BLACK );
text.setWrappingWidth( 140 );
text.setLayoutX( 50.0 );
text.setLayoutY( 15.0 );
sg2D.getChildren().add( text );
// シーンを作成
SubScene subscene = new SubScene( sg2D , rect.getWidth() , rect.getHeight() );
return subscene;
}
}
ソースコードの解説は以下の通り。今回は各々のScene Graphノードについて詳細に説明はせず、プログラムの骨格について解説する。
- create3DScene関数(79行目~)では、3Dオブジェクトを表示するサブシーンを作成している。関数内ではサブシーンのルート・ノードであるsd3Dオブジェクトに作成した3Dオブジェクトを追加している。
- create2DScene関数(120行目~)では、2Dオブジェクトを表示するサブシーンを作成している。関数内ではサブシーンのルート・ノードであるsd2Dオブジェクトに作成した2Dオブジェクトを追加している。
- Start関数(51行目~)では、事前に作成した3Dオブジェクトと2Dオブジェクトを1つのシーンにまとめ、ウィンドウ表示している。
Scene Graphの各クラスについて、詳細な使い方はまた別の記事にて確認する。
■ 参照
- JavaDoc - Class Node